package com.abel.bigwater.tlv.model

import io.netty.buffer.ByteBuf
import io.netty.buffer.Unpooled
import org.joda.time.DateTime
import java.io.ByteArrayOutputStream
import java.net.Inet4Address
import java.net.InetSocketAddress
import java.sql.Timestamp
import java.time.LocalDateTime
import java.time.LocalTime
import java.util.*
import javax.crypto.Cipher
import javax.crypto.SecretKey
import javax.crypto.spec.SecretKeySpec
import kotlin.experimental.and

object TlvHelper {
    /**
     * fill timestamp with bcd.
     */
    fun fillTimestamp(ba: ByteArray, date: Date = Date()): ByteArray {
        if (ba.size != 6) {
            throw TlvInvalidException("timestamp should be size of 6: ${ba.size}")
        }

        val dt = DateTime(date)
        ba[0] = (dt.year % 100).toByte()
        ba[1] = dt.monthOfYear.toByte()
        ba[2] = dt.dayOfMonth.toByte()
        ba[3] = dt.hourOfDay.toByte()
        ba[4] = dt.minuteOfHour.toByte()
        ba[5] = dt.secondOfMinute.toByte()

        return ba
    }

    /**
     * YYMMDDHHMMSS to timestamp
     */
    fun toTimestamp(ba: ByteArray): Date {
        if (ba.size < 2) throw  TlvInvalidException("timestamp should be 1 byte more: ${ba.size}")

        val year = ba[0]
        val month = ba[1]
        val day = if (ba.size > 2) ba[2] else 1
        val hour = if (ba.size > 3) ba[3] else 0
        val minute = if (ba.size > 4) ba[4] else 0
        val second = if (ba.size > 5) ba[5] else 0

        return DateTime(year.toInt(), month.toInt(), day.toInt(), hour.toInt(), minute.toInt(), second.toInt()).toDate()
    }

    fun toBcdString(ba: ByteArray): String {
        val buf = StringBuilder()
        ba.forEach { buf.append(("0" + it.toString(16)).takeLast(2)) }

        return buf.toString()
    }

    fun toBcdArray(str: String): ByteArray {
        val ba = ByteArray(str.length / 2)
        try {
            for (i in 0..str.length - 1 step 2) {
                ba[i / 2] = str.substring(i, i + 2).toByte(16)
            }

            return ba
        } catch (ex: Exception) {
            throw IllegalArgumentException("Invalid bcd string: ${str}", ex)
        }
    }

    fun toHexString(ba: ByteArray?, delimitor: Char? = ' '): String {
        ba ?: return ""

        val buf = StringBuilder()
        ba.forEach {
            val b = it.toInt() and 0x7FF
            buf.append(("0" + b.toString(16)).takeLast(2))
            if (delimitor != null) buf.append(delimitor)
        }

        return buf.toString()
    }

    fun toByteArray(str: String, delimitor: Char = ' '): ByteArray {
        val ba = ByteArrayOutputStream()

        str.split(delimitor).forEach {
            val s1 = it.trim()
            if (s1.length > 2) {
                for (i in 0..s1.length - 1 step 2) {
                    ba.write(s1.substring(i..i + 1).toInt(16))
                }
            } else if (s1.length > 0) {
                ba.write(s1.toInt(16))
            }
        }

        return ba.toByteArray()
    }

    fun toByteArray(ts: LocalTime): ByteArray {
        val ba = ByteArray(3)
        ba[0] = ts.hour.toByte()
        ba[1] = ts.minute.toByte()
        ba[2] = ts.second.toByte()
        return ba
    }

    fun toLocalTime(ba: ByteArray): LocalTime {
        return LocalTime.of(ba[0].toInt(), ba[1].toInt(), ba[2].toInt())
    }

    fun toByteArray(serverIp: InetSocketAddress): ByteArray {
        val bos = Unpooled.buffer()
        bos.writeBytes(serverIp.address.address)
        bos.writeShort(serverIp.port)

        val ba = ByteArray(6)
        return bos.readBytes(ba).let { ba }
    }

    fun toByteArray(ldt: LocalDateTime): ByteArray {
        val ba = ByteArray(6)

        ba[0] = (ldt.year % 100).toByte()
        ba[1] = ldt.monthValue.toByte()
        ba[2] = ldt.dayOfMonth.toByte()
        ba[3] = ldt.hour.toByte()
        ba[4] = ldt.minute.toByte()
        ba[5] = ldt.second.toByte()

        return ba
    }

    fun toLocalDateTime(ba: ByteArray): LocalDateTime {
        if (ba.size < 6) throw IllegalArgumentException("local-date-time should be at least 6: ${ba.size}")

        return LocalDateTime.of((ba[0].toInt() and 0xFF) + 2000, ba[1].toInt(), ba[2].toInt(),
                ba[3].toInt(), ba[4].toInt(), ba[5].toInt())
    }

    fun decryptAes(ba: ByteArray, key: String = "0211633658965411"): ByteArray {
        val keySpec = SecretKeySpec(key.toByteArray(Charsets.US_ASCII), "AES")
        val cipher = Cipher.getInstance("AES/ECB/NoPadding")

        cipher.init(Cipher.DECRYPT_MODE, keySpec)
        val result = cipher.doFinal(ba)

        return result
    }

    fun encryptAes(ba: ByteArray, key: String = "0211633658965411"): ByteArray {
        val padSize = ba.size % 16
        val padBa = if (padSize != 0) {
            val pba = ByteArray(ba.size / 16 * 16 + 16)
            System.arraycopy(ba, 0, pba, 0, ba.size)
            pba
        } else ba

        val keySpec = SecretKeySpec(key.toByteArray(Charsets.US_ASCII), "AES")
        val cipher = Cipher.getInstance("AES/ECB/NoPadding")

        cipher.init(Cipher.ENCRYPT_MODE, keySpec)
        val result = cipher.doFinal(padBa)

        return result

    }
}